<?php

/**
 * 数据库Mongo驱动
 *
 * TODO 尚处于测试阶段
 *
 * @author      jonwang(jonwang@myqee.com)
 * @category    MyQEE
 * @package     System
 * @subpackage  Core
 * @copyright   Copyright (c) 2008-2011 myqee.com
 * @license     http://www.myqee.com/license.html
 */
class MyQEE_Database_Driver_Mongo extends Database_Driver
{
    /**
     * 当前数据库连接
     * @var array
     */
    protected static $_current_databases = array();

    /**
     * 链接寄存器
     * @var array
     */
    protected static $_connection_instance = array();

    /**
     * 记录连接对象数目，当对象销毁到只有1个时，才关闭对应连接
     * @var array
     */
    protected static $_connection_num = array();

    /**
     * 当前表对象
     * @var MongoDB
     */
    protected $_db;

    public function compile($builder, $type = 'selete')
    {
        $find = array();
        if ( ! empty($builder['where']) )
        {
            $find = $this->_compile_conditions($builder['where'], $builder['parameters']);
        }

        if ( $type=='insert' )
        {
            $sql = array(
                'type'  => 'insert',
                'table' => $builder['table'],
                'options' => array(
                    'safe'     => true,
                ),
            );
            if ( count($builder['values'])>1 )
            {
                foreach ($builder['columns'] as $key=>$field)
                {
                    foreach ($builder['values'] as $k=>$v)
                    {
                        $data[$k][$field] = $builder['values'][$k][$key];
                    }
                }
                $sql['datas'] = $data;
            }
            else
            {
                foreach ($builder['columns'] as $key=>$field)
                {
                    $data[$field] = $builder['values'][0][$key];
                }
                $sql['data'] = $data;
            }
        }
        elseif ( $type == 'update' )
        {
            $sql = array(
                'type'  => 'update',
                'table' => $builder['table'],
                'where' => $find,
                'options' => array(
                    'multiple' => true,
                    'safe'     => true,
                ),
            );
            foreach ($builder['set'] as $item)
            {
                if ( $item[2]=='+' )
                {
                    $op = '$inc';
                }
                elseif  ( $item[2]=='-' )
                {
                    $item[1] = - $item[1];
                    $op = '$inc';
                }
                else
                {
                    $op = '$set';
                }
                $sql['data'][$op][$item[0]] = $item[1];
            }
        }
        else
        {
            $sql = array(
                'type'  => $type,
                'table' => $builder['from'][0],
                'find'  => $find,
                'limit' => $builder['limit'],
                'skip'  => $builder['offset'],
            );
            if ( $builder['select'] )
            {
                foreach ($builder['select'] as $item)
                {
                    if ( is_string($item) )
                    {
                        $item = trim($item);
                        if ( preg_match('#^(.*) as (.*)$#', $item , $m) )
                        {
                            $s[$m[1]] = $m[2];
                        }
                        else
                        {
                            $s[$item] = true;
                        }
                    }
                    elseif (is_object($item) && $item instanceof QueryBuilder_Expression)
                    {
                        $s[$item->value()] = true;
                    }
                }
                $sql['select'] = $s;
            }
        }
        return $sql;
    }

    /**
     * 连接数据库
     * @param boolean $use_master 是否使用主数据库
     */
    public function connect($use_master = true)
    {
        $database = $hostname = $port = $socket = $username = $password = $persistent = null;
        extract($this->config['connection']);
        if (!$port>0)
        {
            $port = null;
        }

        # 切换主从服务器
        if ( $use_master )
        {
            if ( $this->_connection_master_id && Database_Driver_Mongo::$_connection_instance[$this->_connection_master_id] )
            {
                if ( $this->_connection != Database_Driver_Mongo::$_connection_instance[$this->_connection_master_id] )
                {
                    $this->_connection = Database_Driver_Mongo::$_connection_instance[$this->_connection_master_id];
                    $this->_connection_id = $this->_connection_master_id;
                }
                $this->_select_db($database);
                return;
            }
            $_connection_name = '_connection_master';
        }
        else
        {
            if ( $this->_connection_slaver_id && Database_Driver_Mongo::$_connection_instance[$this->_connection_slaver_id] )
            {
                if ( $this->_connection != Database_Driver_Mongo::$_connection_instance[$this->_connection_slaver_id] )
                {
                    $this->_connection = Database_Driver_Mongo::$_connection_instance[$this->_connection_slaver_id];
                    $this->_connection_id = $this->_connection_slaver_id;
                }
                $this->_select_db($database);
                return;
            }
            $_connection_name = '_connection_slaver';
        }
        $_connection_id_name = $_connection_name . '_id';

        # 是否需要去连接
        $need_real_content = true;
        # 检查下是否已经有连接连上去了
        if ( is_array($hostname) && Database_Driver_Mongo::$_connection_instance )
        {
            if ( $use_master )
            {
                $hostconfig = $hostname['master'];
            }
            else
            {
                $hostconfig = $hostname['slaver'];
            }
            if ( ! is_array($hostconfig) )
            {
                $hostconfig = array($hostconfig);
            }
            foreach ( $hostconfig as $host )
            {
                $_connection_id = $this->_get_connection_hash($host, $port, $username);
                if ( isset(Database_Driver_Mongo::$_connection_instance[$_connection_id]) )
                {
                    # 设置当前连接
                    $this->$_connection_name = Database_Driver_Mongo::$_connection_instance[$_connection_id];
                    $need_real_content = false;
                    break;
                }
            }
        }

        if ( $need_real_content )
        {
            $try_num = 5;
            for( $i = 1; $i <= $try_num; $i ++ )
            {
                $hostname = $this->_get_rand_host($use_master);
                $_connection_id = $this->_get_connection_hash($hostname, $port, $username);

                # 尝试重连
                try
                {
                    $time = microtime(true);
                    Database_Driver_Mongo::$_connection_instance[$_connection_id] = new Mongo("mongodb://{$username}:{$password}@{$hostname}:{$port}/{$database}");
                    Core::debug()->info($hostname . ' mongo connection time:' . (microtime(true) - $time));
                    $this->$_connection_name = Database_Driver_Mongo::$_connection_instance[$_connection_id];
                    break;
                }
                catch ( Exception $e )
                {
                    $this->$_connection_name = $this->_connection = null;

                    if ( $i == $try_num )
                    {
                        throw $e;
                    }
                }
                if ( $i == $try_num && ! $this->$_connection_name )
                {
                    throw new Exception('数据库链接失败');
                }
            }
        }

        # 记录链接ID
        $this->_connection_id = $this->$_connection_id_name = $_connection_id;

        # 切换当前链接
        $this->_connection = $this->$_connection_name;

        # 连接使用数加1
        if ( ! isset(Database_Driver_Mongo::$_connection_num[$_connection_id]) )
        {
            Database_Driver_Mongo::$_connection_num[$_connection_id] = 1;
        }
        else
        {
            Database_Driver_Mongo::$_connection_num[$_connection_id] ++;
        }

        #选择数据库
        $this->_select_db($database);

        if ( ! empty($this->config['charset']) )
        {
            // Set the character set
            $this->set_charset($this->config['charset']);
        }

        $port = $this->config['connection']['port']?$this->config['connection']['port']:27017;

    }

    /**
     * 关闭链接
     */
    public function close_connect()
    {
        if ( null === $this->_connection_id )
        {
            return;
        }
        Database_Driver_Mongo::$_connection_num[$this->_connection_id] --;
        # 如果主链接使用数<=0，则关闭主链接
        # 注意，当没有采用主从时，$this->_connection_id==$this->_connection_master_id的，所以上面--，则相当于Database::$_connection_num[$this->_connection_master_id]--
        if ( Database_Driver_Mongo::$_connection_num[$this->_connection_master_id] <= 0 && is_object($this->_connection_master) )
        {
            $this->_connection_master->close();
            $this->_connection_master = null;
        }

        if ( $this->_connection_master_id != $this->_connection_slaver_id )
        {
            # 主连接与从连接不相同才需要处理
            Database_Driver_Mongo::$_connection_num[$this->_connection_slaver_id] --;
            # 关闭从链接
            if ( Database_Driver_Mongo::$_connection_num[$this->_connection_slaver_id] <= 0 && is_object($this->_connection_slaver) )
            {
                $this->_connection_slaver->close();
                $this->_connection_slaver = null;
            }
        }

        $this->_connection = null;
        $this->_connection_id = null;
    }

    protected function _select_db($database)
    {
        if ( ! $database ) return;
        if ( ! isset(Database_Driver_Mongo::$_current_databases[$this->_connection_id]) || $database != Database_Driver_Mongo::$_current_databases[$this->_connection_id] )
        {
            $this->_db = $this->_connection->$database;
            if ( ! $this->_db )
            {
                throw new Exception('选择Mongo数据表错误');
            }

            Database_Driver_Mongo::$_current_databases[$this->_connection_id] = $database;
        }
    }

    public function set_charset($charset)
    {
        return true;
    }

    public function escape($value)
    {
        return $value;
    }

    public function quote_table($value)
    {
        return $value;
    }

    public function quote($value)
    {
        return $value;
    }

    /**
     * 查询
     *
     * 目前支持插入、修改、保存（类似Mysql的replace）查询
     *
     * @param array $sql
     * @param string $as_object 是否返回对象
     * @param boolean $use_master 是否使用主数据库，不设置则自动判断
     * @return Database_Driver_MySQLI_Result
     */
    public function query($sql, $as_object = null, $use_master = null)
    {
        $this->connect($use_master);

        $tablename = $this->config['table_prefix'] . $sql['table'];
        $type = strtolower($sql['type']);

        switch ( $type )
        {
            case 'select':
                $result = $this->_db->$tablename->find($sql['find'],(array)$sql['select']);

                if ( $sql['limit'] )
                {
                    $result = $result->limit($sql['limit']);
                }
                if ( $sql['skip'] )
                {
                    $result = $result->skip($sql['skip']);
                }
                if ( $sql['sort'] )
                {
                    $result = $result->sort($sql['sort']);
                }

                return new Database_Driver_Mongo_Result($result, $sql, $as_object ,$this->config );
            case 'update':
                $result = $this->_db->$tablename->update($sql['where'] , $sql['data'] , $sql['options']);
                return $result['n'];
            case 'save':
            case 'insert':
                $result = $this->_db->$tablename->$type($sql['data'] , $sql['options']);
                if ( isset($sql['data']['_id']) && $sql['data']['_id'] instanceof MongoId )
                    return array(
                        (string)$sql['data']['_id'] ,
                        1 ,
                    );
                else
                {
                    return array(
                        '',
                        0,
                    );
                }
            case 'remove':
                $result = $this->_db->$tablename->remove($sql['data'] , $sql['options']);
                return $result['n'];
            default:
                throw new Exception('不支持的操作类型');
        }
    }

    /**
     * Compiles an array of conditions into an SQL partial. Used for WHERE
     * and HAVING.
     *
     * @param   object  Database instance
     * @param   array   condition statements
     * @return  string
     */
    protected function _compile_conditions(array $conditions, $parameters)
    {
        $sql = array();
        foreach ( $conditions as $group )
        {
            // Process groups of conditions
            foreach ( $group as $logic => $condition )
            {
                if ( $condition === '(' )
                {

                }
                elseif ( $condition === ')' )
                {

                }
                else
                {
                    list ( $column, $op, $value ) = $condition;
                    $op = strtolower($op);

                    if ( $op === 'between' && is_array($value) )
                    {
                        list ( $min, $max ) = $value;

                        if ( is_string($min) && array_key_exists($min, $parameters) )
                        {
                            $min = $parameters[$min];
                        }

                        if ( is_string($max) && array_key_exists($max, $parameters) )
                        {
                            $max = $parameters[$max];
                        }

                        $value = array('$gte' => $min , '$lte' => $max);
                    }
                    elseif ($op==='=')
                    {
                        if ( isset($sql[$column]) )
                        {
                            if ( is_array($sql[$column]) )
                            {
                                $sql[$column]['$where'] = $value;
                            }
                            else
                            {
                                $sql[$column] = array('$where' => $value);
                            }
                        }
                        else
                        {
                            $sql[$column] = $value;
                        }
                    }
                    else
                    {
                        $op_arr = array(
                            '>'  => 'gt',
                            '>=' => 'gte',
                            '<'  => 'lt',
                            '<=' => 'lte',
                            '!=' => 'ne',
                            '%'  => 'mod',
                        );
                        if ( isset($op_arr[$op]) )
                        {
                            $op = $op_arr[$op];
                            $sql[$column]['$'.$op] = $value;
                        }

                    }
                }
            }
        }

        return $sql;
    }

}